package uy.org.softtech.cssuite.web.comando;

import static org.springframework.util.Assert.isTrue;
import static org.springframework.util.Assert.notNull;
import static org.springframework.util.StringUtils.hasText;
import static uy.org.softtech.cssuite.entidad.usuario.Permiso.Token.*;
import static uy.org.softtech.cssuite.helpers.HelperPassword.validaPassword;
import static uy.org.softtech.cssuite.helpers.HelperSeguridad.poseeAlguno;
import static uy.org.softtech.cssuite.helpers.HelperTextos.formatearTelefono;
import static uy.org.softtech.cssuite.helpers.HelperTextos.validaCelular;
import static uy.org.softtech.cssuite.helpers.HelperTextos.validaCorreoElectronico;
import static uy.org.softtech.cssuite.helpers.HelperTextos.validaNombre;
import static uy.org.softtech.cssuite.helpers.HelperTextos.validaTelefono;

import java.util.Locale;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;

import uy.org.softtech.cssuite.core.Comando;
import uy.org.softtech.cssuite.core.Modelo;
import uy.org.softtech.cssuite.core.Controlador.Accion;
import uy.org.softtech.cssuite.entidad.usuario.Rol;
import uy.org.softtech.cssuite.entidad.usuario.Usuario;
import uy.org.softtech.cssuite.excepciones.EntidadNoActualizadaException;
import uy.org.softtech.cssuite.excepciones.EntidadNoCreadaException;
import uy.org.softtech.cssuite.excepciones.EntidadNoEliminadaException;
import uy.org.softtech.cssuite.excepciones.EntidadNoEncontradaException;
import uy.org.softtech.cssuite.servicios.ServiciosUsuarios;
import uy.org.softtech.cssuite.web.modelo.ModeloUsuario;

/**
 * Implementación de {@link Comando} encargado de procesar la solicitud de creacion y/o actualización de la información de un usuario
 * 
 * @author fabian.lobo
 * @see Comando#ejecutar(Modelo, BindingResult)
 */
@Component("comando-usuario")
public class ComandoUsuario extends Comando {
	
	private Logger log = Logger.getLogger(getClass());
	
	@Resource(name = "servicios-usuarios" )
	private ServiciosUsuarios serviciosUsuarios;
	
	@Resource(name = "codificador-password")
	private PasswordEncoder codificadorPassword;
	
	/**
	 * Método para verificar que todos los parámetros han sido seteados correctamente.
	 * 
	 * @author fabian.lobo
	 */
	@PostConstruct
	public void inicializacion(){
		super.inicializacion();
		
		notNull(serviciosUsuarios(), "La propiedad [serviciosUsuarios] de [" + getClass().getName() + "] no ha sido establecida.");
		notNull(codificadorPassword(), "La propiedad [codificadorPassword] de [" + getClass().getName() + "] no ha sido establecida.");
		
		log.info("Comando encargado de procesar la solicitud de actualización de la información de un usuario ha sido inicializado.");
	}

	@Override
	public void validaModelo(Modelo modelo, BindingResult errores) {
		notNull(modelo, "El modelo no puede ser nulo.");
		isTrue(modelo instanceof ModeloUsuario, "El modelo no es el correcto para este comando.");
		
		ModeloUsuario modeloUsuario = (ModeloUsuario) modelo;
		
		notNull(modeloUsuario.getAccion(), "Accion no puede ser nulo en este modelo.");
		notNull(modeloUsuario.getLocale(), "Locale no puede ser nulo en este modelo.");
		
		Locale locale = modeloUsuario.getLocale();
		Accion accion = modeloUsuario.getAccion();
		
		//CAMPOS NO EDITABLES -> ID
		switch(accion) {
		case ACTUALIZAR:
		case BLOQUEAR:
		case DESBLOQUEAR:
		case ELIMINAR:
			notNull(modeloUsuario.getId(), "ID no puede ser nulo en este modelo.");
			break;
		}
					
		//CAMPOS EDITABLES REQUERIDOS -> PRIMER NOMBRE, APELLIDO
		String primerNombre = modeloUsuario.getPrimerNombre();
		String apellido = modeloUsuario.getApellido();
		
		//CAMPOS EDITABLES OPCIONALES -> SEGUNDO NOMBRE, CORREO ELECTRONICO, TELEFONO, CELULAR
		String segundoNombre = modeloUsuario.getSegundoNombre();
		String correoElectronico = modeloUsuario.getCorreoElectronico();
		String telefono = modeloUsuario.getTelefono();
		String celular = modeloUsuario.getCelular();
		
		//CAMPOS REQUERIDOS DURANTE CREACION
		String password = modeloUsuario.getPassword();
		String confirmar = modeloUsuario.getConfirmar();

		switch(accion) {
		case CREAR:
			if(hasText(password)) {
				if(!validaPassword(password)) {
					//La nueva contraseña no cumple con las reglas
					errores.addError(new FieldError("modeloUsuario", "password", fuenteMensajes().getMessage("mensaje.passwordInvalida", null, "El texto ingresado no cumple con las reglas requeridas.", locale)));
				}
			} else {
				//La nueva contraseña no puede ser nula o espacios en blanco
				errores.addError(new FieldError("modeloUsuario", "password", fuenteMensajes().getMessage("mensaje.passwordRequerida", null, "Por favor, ingrese la contraseña.", locale)));
			}
			if(hasText(confirmar)) {
				if(hasText(password) && !password.equals(confirmar)) {
					//La nueva contraseña y la confirmación no son iguales
					errores.addError(new FieldError("modeloUsuario", "confirmar", fuenteMensajes().getMessage("mensaje.passwordDiferentes", null, "Los textos ingresados no coinciden.", locale)));
				}
			} else {
				//La contraseña debe ser confirmada
				errores.addError(new FieldError("modeloUsuario", "confirmar", fuenteMensajes().getMessage("mensaje.passwordNoConfirmada", null, "Debe confirmar la contraseña.", locale)));
			}
			if(!hasText(correoElectronico)) {
				//Debe ingresar una dirección de correo electrónico
				errores.addError(new FieldError("modeloUsuario", "correoElectronico", fuenteMensajes().getMessage("mensaje.correoElectronicoRequerido", null, "Por favor, ingrese un correo electrónico.", locale)));
			}
		case ACTUALIZAR:
			if(hasText(primerNombre)) {
				if (!validaNombre(primerNombre)) {
			    	//El primer nombre contiene caracteres extraños
					errores.addError(new FieldError("modeloUsuario", "primerNombre", fuenteMensajes().getMessage("mensaje.textoInvalido", null, "El texto ingresado contiene caracteres que no corresponden.", locale)));
			    }
			} else {
				//El primer nombre no puede ser nulo o espacios en blanco
				errores.addError(new FieldError("modeloUsuario", "primerNombre", fuenteMensajes().getMessage("mensaje.primerNombreRequerido", null, "Por favor, ingrese el primer nombre.", locale)));
			}
			if(hasText(apellido)) {
				if (!validaNombre(apellido)) {
			    	//El apellido contiene caracteres extraños
					errores.addError(new FieldError("modeloUsuario", "apellido", fuenteMensajes().getMessage("mensaje.textoInvalido", null, "El texto ingresado contiene caracteres que no corresponden.", locale)));
			    }
			} else {
				//El apellido no puede ser nulo o espacios en blanco
				errores.addError(new FieldError("modeloUsuario", "apellido", fuenteMensajes().getMessage("mensaje.apellidoRequerido", null, "Por favor, ingrese el apellido.", locale)));
			}
			if(hasText(segundoNombre)) {
				if (!validaNombre(segundoNombre)) {
			    	//El segundo nombre contiene caracteres extraños
					errores.addError(new FieldError("modeloUsuario", "segundoNombre", fuenteMensajes().getMessage("mensaje.textoInvalido", null, "El texto ingresado contiene caracteres que no corresponden.", locale)));
			    }
			}
			if(hasText(correoElectronico)) {
				if(!validaCorreoElectronico(correoElectronico)) {
					//Debe ser una dirección de correo electrónico
					errores.addError(new FieldError("modeloUsuario", "correoElectronico", fuenteMensajes().getMessage("mensaje.correoElectronicoInvalido", null, "Por favor, ingrese un correo electrónico válido.", locale)));
				}
			}
			if(hasText(telefono)) {
				if(!validaTelefono(telefono, locale)) {
					//Debe ser teléfono fijo válido
					errores.addError(new FieldError("modeloUsuario", "telefono", fuenteMensajes().getMessage("mensaje.telefonoFijoInvalido", null, "Por favor, ingrese un número de teléfono fijo válido.", locale)));
				}
			}
			if(hasText(celular)) {
				if(!validaCelular(celular, locale)) {
					//Debe ser teléfono celular válido
					errores.addError(new FieldError("modeloUsuario", "celular", fuenteMensajes().getMessage("mensaje.telefonoCelularInvalido", null, "Por favor, ingrese un número de teléfono móvil válido.", locale)));
				}
			}
			break;
		default:
			break;
		}
	}
	
	@Override
	public void ejecutarComando(Modelo modelo, BindingResult errores) {
		notNull(modelo, "El modelo no puede ser nulo.");
		isTrue(modelo instanceof ModeloUsuario, "El modelo no es el correcto para este comando.");
		
		ModeloUsuario modeloUsuario = (ModeloUsuario) modelo;
		
		notNull(modeloUsuario.getAccion(), "Accion no puede ser nulo en este modelo.");
		notNull(modeloUsuario.getLocale(), "Locale no puede ser nulo en este modelo.");
		
		Locale locale = modeloUsuario.getLocale();
		Accion accion = modeloUsuario.getAccion();
		
		//CAMPOS NO EDITABLES -> ID
		switch(accion) {
		case ACTUALIZAR:
		case BLOQUEAR:
		case DESBLOQUEAR:
		case ELIMINAR:
			notNull(modeloUsuario.getId(), "ID no puede ser nulo en este modelo.");
			break;
		}
		
		Usuario usuario = null;
		switch (accion) {
		case BLOQUEAR:
			try {
				serviciosUsuarios().bloquearUsuario(modeloUsuario.getId());
			} catch (EntidadNoEncontradaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			} catch (EntidadNoActualizadaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			}
			break;
		case DESBLOQUEAR:
			try {
				serviciosUsuarios().desbloquearUsuario(modeloUsuario.getId());
			} catch (EntidadNoEncontradaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			} catch (EntidadNoActualizadaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			}
			break;
		case ELIMINAR:
			try {
				serviciosUsuarios().eliminarUsuario(modeloUsuario.getId());
			} catch (EntidadNoEncontradaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			} catch (EntidadNoEliminadaException e) {
				log.error(e);
				errores.addError(new ObjectError("modeloUsuario", e.getMessage()));
			}
			break;
		case CREAR:
			try {
				Rol rol = serviciosUsuarios().buscarRol(modeloUsuario.getIdRol());
				
				usuario = new Usuario();
				usuario.setRol(rol);
				usuario.setPrimerNombre(modeloUsuario.getPrimerNombre());
				usuario.setSegundoNombre(modeloUsuario.getSegundoNombre());
				usuario.setApellido(modeloUsuario.getApellido());
				usuario.setCorreoElectronico(modeloUsuario.getCorreoElectronico());
				usuario.setTelefono(formatearTelefono(modeloUsuario.getTelefono(), locale));
				usuario.setCelular(formatearTelefono(modeloUsuario.getCelular(), locale));
				usuario.setUsuario(modeloUsuario.getCorreoElectronico());
				usuario.setPassword(codificadorPassword().encode(modeloUsuario.getPassword()));
				usuario.setComentarios(modeloUsuario.getComentarios());
				
				usuario = serviciosUsuarios().crear(usuario, SERVICIO);
			} catch (EntidadNoEncontradaException enee) {
				log.error(enee);
				errores.addError(new ObjectError("modeloUsuario", fuenteMensajes().getMessage("mensaje.datosNoGuardados", null, "No hemos podido almacenar los datos. Por favor, contacte al administrador del sistema.", locale)));
			} catch (EntidadNoCreadaException ence) {
				log.error(ence);
				errores.addError(new ObjectError("modeloUsuario", fuenteMensajes().getMessage("mensaje.datosNoGuardados", null, "No hemos podido almacenar los datos. Por favor, contacte al administrador del sistema.", locale)));
			}
			
			break;
		case ACTUALIZAR:
			try {
				usuario = serviciosUsuarios().buscarUsuario(modeloUsuario.getId());
				usuario.setPrimerNombre(modeloUsuario.getPrimerNombre());
				usuario.setSegundoNombre(modeloUsuario.getSegundoNombre());
				usuario.setApellido(modeloUsuario.getApellido());
				usuario.setCorreoElectronico(modeloUsuario.getCorreoElectronico());
				usuario.setTelefono(formatearTelefono(modeloUsuario.getTelefono(), locale));
				usuario.setCelular(formatearTelefono(modeloUsuario.getCelular(), locale));
				usuario.setComentarios(modeloUsuario.getComentarios());
				
				if(poseeAlguno(ACTUALIZAR_USUARIO)) {
					Rol rol = serviciosUsuarios().buscarRol(modeloUsuario.getIdRol());
					usuario.setRol(rol);
				}
				
				serviciosUsuarios().actualizar(usuario, SERVICIO);
				
			} catch (EntidadNoEncontradaException enee) {
				log.error(enee);
				errores.addError(new ObjectError("modeloUsuario", fuenteMensajes().getMessage("mensaje.datosNoModificados", null, "No hemos podido actualizar los datos. Por favor, contacte al administrador del sistema.", locale)));
			} catch (EntidadNoActualizadaException enae) {
				log.error(enae);
				errores.addError(new ObjectError("modeloUsuario", fuenteMensajes().getMessage("mensaje.datosNoModificados", null, "No hemos podido actualizar los datos. Por favor, contacte al administrador del sistema.", locale)));
			}
			break;
		default:
			break;
		}
	}

	/*
	 *  GETTERS
	 */
	public ServiciosUsuarios serviciosUsuarios() {
		return serviciosUsuarios;
	}
	public PasswordEncoder codificadorPassword() {
		return codificadorPassword;
	}
}
